package dal;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import model.IModel;
import model.IModelExt;
import util.CacheUtil;
import util.RightUtil;
import util.SessionUtil;

public class GenericDal<T extends IModel, TE extends IModelExt> {

	private Connection _dbConn = null;
	private T _utilModel;

	public GenericDal(T utilModel) {
		_utilModel = utilModel;
	}

	private boolean _useExternalConnection = false;

	public GenericDal(T utilModel, Connection dbConn) {
		_utilModel = utilModel;
		_dbConn = dbConn;
		_useExternalConnection = true;
	}

	private Connection getDbConnection() throws Exception {
		if (_dbConn == null || _dbConn.isClosed()) {
			_dbConn = ConnectionProvider.genConnection();
		}

		return _dbConn;
	}

	public T select(T query) throws Exception {
		T model = null;
		if (RightUtil.checkSelectRight(query)) {
			Connection conn = getDbConnection();
			PreparedStatement st = _utilModel.genSelectStatement(conn, query);
			try {
				ResultSet rs = st.executeQuery();
				if (rs.next())
					model = (T) _utilModel.readFromResultSet(rs);
				rs.close();
			} catch (SQLException e) {
				// TODO: handle exception
			} finally {
				if (!_useExternalConnection) {
					conn.close();
				}
			}
		}
		return model;
	}

	public List<T> selectList(TE query) throws Exception {
		List<T> models = null;
		if (RightUtil.checkSelectListRight(query)) {
			Connection conn = getDbConnection();
			PreparedStatement st = _utilModel.genSelectListStatement(conn,
					query);
			try {
				ResultSet rs = st.executeQuery();
				models = new ArrayList<T>();

				while (rs.next()) {
					T model = (T) _utilModel.readFromResultSet(rs);
					models.add(model);
				}

				rs.close();

			} catch (SQLException e) {
				// TODO: handle exception
				throw e;
			} finally {
				if (!_useExternalConnection) {
					conn.close();
				}
			}
		}
		return models;
	}

	public int insert(T model) throws Exception {
		int result = Integer.MIN_VALUE;
		if (RightUtil.checkInsertRight(model)) {

			model.setCreator(SessionUtil.getCurrentUserID());

			Connection conn = getDbConnection();
			PreparedStatement st = _utilModel.genInsertStatement(conn, model);
			try {
				ResultSet rs = st.executeQuery();
				if (rs.next()) {
					result = rs.getInt(1);
				}
				rs.close();
			} catch (SQLException e) {
				throw e;
			} finally {
				if (!_useExternalConnection) {
					conn.close();
				}
			}
		}

		if (result >= 0) {
			model.setId(result);
			CacheUtil.addToCache(model);
		}

		return result;
	}

	public int update(T model) throws Exception {
		int result = Integer.MIN_VALUE;
		if (RightUtil.checkUpdateRight(model)) {

			model.setUpdator(SessionUtil.getCurrentUserID());

			Connection conn = getDbConnection();
			PreparedStatement st = _utilModel.genUpdateStatement(conn, model);
			try {
				ResultSet rs = st.executeQuery();
				if (rs.next()) {
					result = rs.getInt(1);
				}
				rs.close();
			} catch (SQLException e) {
				throw e;
			} finally {
				if (!_useExternalConnection) {
					conn.close();
				}
			}
		}

		if (result >= 0) {
			CacheUtil.updateCache(model);
		}

		return result;
	}

	public int delete(T query) throws Exception {
		int result = Integer.MIN_VALUE;
		if (RightUtil.checkDeleteRight(query)) {
			Connection conn = getDbConnection();
			PreparedStatement st = _utilModel.genDeleteStatement(conn, query);

			try {
				ResultSet rs = st.executeQuery();
				if (rs.next()) {
					result = rs.getInt(1);
				}
				rs.close();
			} catch (SQLException e) {
				throw e;
			} finally {
				if (!_useExternalConnection) {
					conn.close();
				}
			}
		}

		if (result >= 0) {
			CacheUtil.deleteCache(query);
		}

		return result;
	}

	public int count(TE query) throws Exception {
		int result = Integer.MIN_VALUE;
		if (RightUtil.checkCountRight(query)) {
			Connection conn = getDbConnection();
			PreparedStatement st = _utilModel.genCountStatement(conn, query);

			try {
				ResultSet rs = st.executeQuery();
				if (rs.next()) {
					result = rs.getInt(1);
				}
				rs.close();
			} finally {
				if (!_useExternalConnection) {
					conn.close();
				}
			}
		}
		return result;
	}

	public static int execute(PreparedStatement st) throws SQLException {
		ResultSet rs = st.executeQuery();
		int result = rs.getInt(1);
		rs.close();
		return result;
	}

	public List<T> executeQuery(PreparedStatement st) throws SQLException {
		ResultSet rs = st.executeQuery();
		List<T> models = new ArrayList<T>();
		while (rs.next()) {
			T model = (T) _utilModel.readFromResultSet(rs);
			models.add(model);
		}
		rs.close();
		return models;
	}

}
